// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#include "coin2/exchange/bybit/order_v3/parser.h"

#include <nlohmann/json.hpp>

namespace coin2::exchange::bybit::order_v3 {

bool BybitParser::IsBybitSpotErrMessage(const JsonValue& doc) {
  if (doc.HasMember("retCode")) {
    return (std::string(doc["retCode"].GetString()) != "0");
  }

  return false;
}

int64_t BybitParser::GetProcOrderId(const JsonValue& data) {
  if (data.HasMember("orderLinkId") && data["orderLinkId"].IsString()) {
    if (!data["orderLinkId"].IsNull()) {
      return stoll(data["orderLinkId"].GetString());
    }
  }
  return 0;
}

int64_t BybitParser::GetWsProcOrderId(const JsonValue& data) {
  if (data.HasMember("c") && data["c"].IsString()) {
    if (!data["c"].IsNull()) {
      return stoll(data["c"].GetString());
    }
  }
  return 0;
}

coin::proto::OrderSide BybitParser::GetOrderSide(const std::string& side) {
  if (side == "BUY") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "SELL") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

coin::proto::OrderType BybitParser::GetOrderType(const std::string& type) {
  if (type == "LIMIT") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "MARKET") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void BybitParser::SetOrderStatus(ProductOrderElement* order, const std::string& status) {
  if ((status == "NEW") || (status == "PENDING_NEW") || (status == "PARTIALLY_FILLED")) {
    order->set_is_live(true);
  } else if (status == "CANCELED" || status == "PARTIALLY_FILLED_CANCELLED") {
    order->set_is_live(false);
    order->set_cancel_confirmed(true);
  } else if (status == "FILLED") {
    order->set_is_live(false);
    order->set_fully_filled(true);
  } else if (status == "PENDING_CANCEL" || status == "REJECTED") {
    order->set_is_live(false);
  } else {
    LOG(ERROR) << "[SetOrderStatus] Unknown order status: " << status;
  }
}

bool BybitParser::IsBalanceUpdate(const JsonValue& json) {
  if (json.HasMember("topic")) {
    return (json["topic"] == "outboundAccountInfo");
  }
  return false;
}

bool BybitParser::IsOrderUpdate(const JsonValue& json) {
  if (json.HasMember("topic")) {
    return (json["topic"] == "order");
  }
  return false;
}

bool BybitParser::IsTradeUpdate(const JsonValue& json) {
  if (json.HasMember("topic")) {
    return (json["topic"] == "ticketInfo");
  }
  return false;
}

void BybitParser::ParseAccountBalance() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseAccountBalance] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(doc);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance BybitParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }
  AccountBalance acc_balance;
  acc_balance.set_exchange("Bybit");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BybitParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "balances": [
        {
          "coin": "BTC",
          "coinId": "BTC",
          "total": "0.20378018343",
          "free": "0.20378018343",
          "locked": "0"
        }
      ]
    },
    "retExtMap": {},
    "retExtInfo": {},
    "time": 1659346887407
  }
  */
  if (IsBybitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] Error message\n" << JsonToString(doc);
    return false;
  }

  if (!doc.HasMember("result") || !doc["result"].HasMember("balances") ||
      !doc["result"]["balances"].IsArray()) {
    LOG(ERROR) << "[ParseBalance] Invalid data format\n" << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["result"]["balances"].GetArray()) {
    if (!data.HasMember("coin") || !data.HasMember("total") || !data.HasMember("free")) {
      continue;
    }
    CurrencyBalance currency_balance;
    currency_balance.set_currency_native(data["coin"].GetString());
    auto total = stod(data["total"].GetString());
    auto avail = stod(data["free"].GetString());
    currency_balance.set_available(avail);
    currency_balance.set_total(total);
    currency_balance.set_hold(total - avail);
    raw_balance_map->emplace(currency_balance.currency_native(), currency_balance);
  }

  return true;
}

void BybitParser::ParseOpenOrderInfo() {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "list": [
        {
          "accountId": "533287",
          "symbol": "BTCUSDT",
          "orderLinkId": "spotx004",
          "orderId": "1210858291884732160",
          "orderPrice": "23500",
          "orderQty": "0.02",
          "execQty": "0",
          "cummulativeQuoteQty": "0",
          "avgPrice": "0",
          "status": "NEW",
          "timeInForce": "GTC",
          "orderType": "LIMIT_MAKER",
          "side": "SELL",
          "stopPrice": "0.0",
          "icebergQty": "0.0",
          "createTime": 1659081556722,
          "updateTime": 1659081556740,
          "isWorking": "1"
        }
      ]
    },
    "retExtInfo": {},
    "time": 1659081570356
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseOpenOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBybitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseOpenOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("result") || !doc["result"].HasMember("list") ||
      !doc["result"]["list"].IsArray()) {
    LOG(ERROR) << "[ParseOpenOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc["result"]["list"].GetArray()) {
    if (!data.HasMember("symbol")) {
      continue;
    }
    std::string native_symbol = data["symbol"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(order, data);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BybitParser::ParseOrderInfo(ProductOrderElement* order, const JsonValue& data) {
  order->set_exchange_type(ExchangeType::Bybit);
  order->set_market_type(MarketType::Spot);
  order->set_api_version("v3");
  order->set_exchange_order_id(data["orderId"].GetString());
  order->set_symbol(data["symbol"].GetString());
  order->set_price(stod(data["orderPrice"].GetString()));
  order->set_qty(std::fabs(stod(data["orderQty"].GetString())));
  order->set_proc_order_id(GetProcOrderId(data));
  order->set_side(GetOrderSide(data["side"].GetString()));
  order->set_order_type(GetOrderType(data["orderType"].GetString()));
  order->set_timestamp(GetCurrentTimestamp());
  SetOrderStatus(order, data["status"].GetString());
}

void BybitParser::ParseHistoryOrderInfo() {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "list": [
        {
          "accountId": "533287",
          "symbol": "BTCUSDT",
          "orderLinkId": "spotx003",
          "orderId": "1210856408331857664",
          "orderPrice": "23800",
          "orderQty": "0.02",
          "execQty": "0",
          "cummulativeQuoteQty": "0",
          "avgPrice": "0",
          "status": "REJECTED",
          "timeInForce": "GTC",
          "orderType": "LIMIT_MAKER",
          "side": "BUY",
          "stopPrice": "0.0",
          "icebergQty": "0.0",
          "createTime": 1659081332185,
          "updateTime": 1659081332225,
          "isWorking": "1"
        }
      ]
    },
    "retExtInfo": {},
    "time": 1659082630638
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseHistoryOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBybitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseHistoryOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("result") || !doc["result"].HasMember("list") ||
      !doc["result"]["list"].IsArray()) {
    LOG(ERROR) << "[ParseHistoryOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc["result"]["list"].GetArray()) {
    if (!data.HasMember("symbol")) {
      continue;
    }
    std::string native_symbol = data["symbol"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(order, data);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BybitParser::ParseFillOrderInfo() {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "list": [
        {
          "symbol": "BTCUSDT",
          "id": "1210346127973428992",
          "orderId": "1210073515485572864",
          "tradeId": "2100000000001769786",
          "orderPrice": "20500",
          "orderQty": "0.02",
          "execFee": "0.00002",
          "feeTokenId": "BTC",
          "creatTime": "1659020488738",
          "isBuyer": "0",
          "isMaker": "0",
          "matchOrderId": "1210346015893229312",
          "makerRebate": "0",
          "executionTime": "1659020502026"
        }
      ]
    },
    "retExtMap": {},
    "retExtInfo": {},
    "time": 1659084254366
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBybitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("result") || !doc["result"].HasMember("list") ||
      !doc["result"]["list"].IsArray()) {
    LOG(ERROR) << "[ParseFillOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : doc["result"]["list"].GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->Clear();
    ParseFillInfo(fill, data);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void BybitParser::ParseFillInfo(ProductFillElement* fill, const JsonValue& data) {
  fill->set_symbol(data["symbol"].GetString());
  fill->set_exchange_order_id(data["orderId"].GetString());
  fill->set_price(stod(data["orderPrice"].GetString()));
  fill->set_qty(std::fabs(stod(data["orderQty"].GetString())));
  fill->set_fill_id(data["tradeId"].GetString());
  fill->set_fee(stod(data["execFee"].GetString()));
  fill->set_fee_currency(data["feeTokenId"].GetString());
  fill->set_side(
      std::string(data["isBuyer"].GetString()) == "0" ? coin::proto::OrderSide::BUY_ORDER
                                                      : coin::proto::OrderSide::SELL_ORDER);
  fill->set_fill_type(
      std::string(data["isMaker"].GetString()) == "0" ? coin::proto::FillType::MAKER_FILL_TYPE
                                                      : coin::proto::FillType::TAKER_FILL_TYPE);
  fill->set_fill_timestamp(GetCurrentTimestamp());
}

void BybitParser::ParseSubmitResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus error";
    resp->set_error_code(OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  if (json.HasMember("retCode") && json.HasMember("retMsg")) {
    auto error_code = GetOrderErrorCode(json["retCode"], json["retMsg"]);
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    if (error_code != OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseSubmitResponse] Error response\n" << http_context->res;
      return;
    }
  }

  if (json.HasMember("result") && json["result"].HasMember("orderId")) {
    resp->set_exchange_order_id(json["result"]["orderId"].GetString());
  } else {
    resp->set_error_code(OrderErrorCode::ORDER_API_ERROR);
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(json);
    return;
  }
  resp->set_success(true);
}

void BybitParser::ParseCancelResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus error";
    resp->set_error_code(OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  if (json.HasMember("retCode") && json.HasMember("retMsg")) {
    auto error_code = GetOrderErrorCode(json["retCode"], json["retMsg"]);
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    if (error_code != OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseCancelResponse] Error response\n" << http_context->res;
      return;
    }
  }

  if (json.HasMember("result") && json["result"].HasMember("orderId")) {
    resp->set_exchange_order_id(json["result"]["orderId"].GetString());
  } else {
    resp->set_error_code(OrderErrorCode::ORDER_API_ERROR);
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(json);
    return;
  }

  resp->set_success(true);
  resp->set_cancel_confirmed(true);
}

void BybitParser::ParseWsBalanceInfoList(const JsonDocument& document) {
  /*
  {
    "type": "snapshot",
    "topic": "outboundAccountInfo",
    "ts": "1662107217641",
    "data": [
      {
        "e": "outboundAccountInfo",
        "E": "1662107217640",
        "T": true,
        "W": true,
        "D": true,
        "B": [
          {
            "a": "USDT",
            "f": "176.81254174",
            "l": "201.575"
          }
        ]
      }
    ]
  }
  */
  if (!is_account_balance_initialized_) {
    return;
  }
  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Bybit");
  acc_balance->set_market_type("Spot");
  bool acc_balance_found = false;
  for (const auto& data : document["data"].GetArray()) {
    if (!data.HasMember("B")) {
      continue;
    }
    for (const auto& b : data["B"].GetArray()) {
      if (!b.HasMember("a") || !b.HasMember("f") || !b.HasMember("l")) {
        continue;
      }
      if (!symcache_->has_currency_native(b["a"].GetString())) {
        continue;
      }
      auto* currency = symcache_->GetCurrencyByNative(b["a"].GetString());
      auto* currency_balance = acc_balance->add_each_balance();
      currency_balance->set_currency(currency->currency());
      currency_balance->set_currency_native(currency->native_currency());
      auto avail = stod(b["f"].GetString());
      auto hold = stod(b["l"].GetString());
      currency_balance->set_total(avail + hold);
      currency_balance->set_available(avail);
      currency_balance->set_hold(hold);
      acc_balance_found = true;
    }
  }

  if (acc_balance_found) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
  }
}

void BybitParser::ParseWsOrderInfoList(const JsonDocument& document) {
  /*
  {
    "type": "snapshot",
    "topic": "order",
    "ts": "1662348310441",
    "data": [
      {
        "e": "order",
        "E": "1662348310441",
        "s": "BTCUSDT",
        "c": "spotx008",
        "S": "BUY",
        "o": "MARKET_OF_QUOTE",
        "f": "GTC",
        "q": "20",
        "p": "0",
        "X": "CANCELED",
        "i": "1238261807653647872",
        "M": "1238225004531834368",
        "l": "0.001007",
        "z": "0.001007",
        "L": "19842.02",
        "n": "0",
        "N": "BTC",
        "u": true,
        "w": true,
        "m": false,
        "O": "1662348310368",
        "Z": "19.98091414",
        "A": "0",
        "C": false,
        "v": "0",
        "d": "NO_LIQ",
        "t": "2100000000002220938"
      }
    ]
  }
  */
  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["data"].GetArray()) {
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Bybit);
    order->set_market_type(MarketType::Spot);
    order->set_api_version("v3");
    order->set_exchange_order_id(data["i"].GetString());
    order->set_symbol(data["s"].GetString());
    order->set_price(stod(data["p"].GetString()));
    order->set_qty(std::fabs(stod(data["q"].GetString())));
    order->set_proc_order_id(GetWsProcOrderId(data));
    order->set_side(GetOrderSide(data["S"].GetString()));
    order->set_order_type(GetOrderType(data["o"].GetString()));
    order->set_timestamp(GetCurrentTimestamp());
    SetOrderStatus(order, data["X"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BybitParser::ParseWsTradeInfoList(const JsonDocument& document) {
  /*
  {
    "type": "snapshot",
    "topic": "ticketInfo",
    "ts": "1662348310388",
    "data": [
      {
        "e": "ticketInfo",
        "E": "1662348310386",
        "s": "BTCUSDT",
        "q": "0.001007",
        "t": "1662348310373",
        "p": "19842.02",
        "T": "2100000000002220938",
        "o": "1238261807653647872",
        "c": "spotx008",
        "O": "1238225004531834368",
        "a": "533287",
        "A": "642908",
        "m": false,
        "S": "BUY"
      }
    ]
  }
  */
  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : document["data"].GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(data["s"].GetString());
    fill->set_fill_timestamp(GetCurrentTimestamp());
    fill->set_exchange_order_id(data["o"].GetString());
    fill->set_price(stod(data["p"].GetString()));
    fill->set_qty(std::fabs(stod(data["q"].GetString())));
    fill->set_fill_id(data["T"].GetString());
    fill->set_side(GetOrderSide(data["S"].GetString()));
    fill->set_fill_type(
        data["m"].GetBool() ? coin::proto::FillType::MAKER_FILL_TYPE
                            : coin::proto::FillType::TAKER_FILL_TYPE);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

bool BybitParser::ParseHeader(
    const std::shared_ptr<AsioHttpContext>& http_context,
    std::string* rate_limit_info) {
  bool ret = false;
  nlohmann::json json = {
      {"X-Bip-Get-Limit-Status", -1},
      {"X-Bip-Post-Limit-Status", -1},
      {"X-Bapi-Limit-Status", -1},
      {"X-Bapi-Limit", -1},
      {"X-Bapi-Limit-Reset-Timestamp", -1},
      {"X-Bapi-Limit-Endpoint", ""},
      {"bind_ip", http_context->bind_ip}};

  const auto& status = http_context->res.result_int();
  if (status == 403) {
    json["X-Bip-Get-Limit-Status"] = 0;
    json["X-Bip-Post-Limit-Status"] = 0;
    ret = true;
  } else {
    const auto& header = http_context->res.base();
    if (header.count("X-Bapi-Limit-Status") > 0) {
      json["X-Bapi-Limit-Status"] = std::stoi(header.at("X-Bapi-Limit-Status").data());
      ret = true;
    }
    if (header.count("X-Bapi-Limit") > 0) {
      json["X-Bapi-Limit"] = std::stoi(header.at("X-Bapi-Limit").data());
      ret = true;
    }
    if (header.count("X-Bapi-Limit-Reset-Timestamp") > 0) {
      json["X-Bapi-Limit-Reset-Timestamp"] =
          std::stoll(header.at("X-Bapi-Limit-Reset-Timestamp").data());
      ret = true;
    }
    std::string target = http_context->target;
    const auto pos = target.find("?");
    json["X-Bapi-Limit-Endpoint"] = pos == std::string::npos ? target : target.substr(0, pos);
  }
  if (ret) {
    *rate_limit_info = json.dump();
  }

  return ret;
}

void BybitParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  DLOG(INFO) << "OG ws msg: " << JsonToString(document);
  if (IsOrderUpdate(document)) {
    ParseWsOrderInfoList(document);
  } else if (IsTradeUpdate(document)) {
    ParseWsTradeInfoList(document);
  } else if (IsBalanceUpdate(document)) {
    ParseWsBalanceInfoList(document);
  }
}

auto BybitParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  CHECK(msg.type == OrderSubTopicId::REST_MESSAGE);

  std::string rate_limit_info;
  if (ParseHeader(msg.http_context, &rate_limit_info)) {
    mb_->PublishEvent("bybit_spot/req_rate_limit", reinterpret_cast<void*>(&rate_limit_info));
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseAccountBalance();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseOpenOrderInfo();
      break;
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseHistoryOrderInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseFillOrderInfo();
      break;
    default: {
      LOG(ERROR) << "[ParseRawMessage] Unknown query type\n" << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::bybit::order_v3
